﻿namespace AutoEvo;

using System;
using Newtonsoft.Json;

[JSONDynamicTypeAllowed]
public class ChunkCompoundPressure : SelectionPressure
{
    // Needed for translation extraction
    // ReSharper disable ArrangeObjectCreationWhenTypeEvident
    private static readonly LocalizedString NameString = new LocalizedString("MICHE_CHUNK_PRESSURE");

    // ReSharper restore ArrangeObjectCreationWhenTypeEvident

    private readonly CompoundDefinition atp = SimulationParameters.GetCompound(Compound.ATP);
    private readonly CompoundDefinition sulfur = SimulationParameters.GetCompound(Compound.Hydrogensulfide);

    [JsonProperty]
    private readonly string chunkType;

    [JsonProperty]
    private readonly LocalizedString readableName;

    private readonly CompoundDefinition compound;
    private readonly CompoundDefinition compoundOut;

    // Needed for saving to work
    [JsonProperty(nameof(compound))]
    private readonly Compound compoundRaw;

    [JsonProperty(nameof(compoundOut))]
    private readonly Compound compoundOutRaw;

    public ChunkCompoundPressure(string chunkType, LocalizedString readableName, Compound compound,
        Compound compoundOut, float weight) : base(weight, [])
    {
        compoundRaw = compound;
        compoundOutRaw = compoundOut;
        this.compound = SimulationParameters.GetCompound(compound);
        this.compoundOut = SimulationParameters.GetCompound(compoundOut);
        this.chunkType = chunkType;
        this.readableName = readableName;
    }

    [JsonIgnore]
    public override LocalizedString Name => NameString;

    public override float Score(Species species, Patch patch, SimulationCache cache)
    {
        if (!patch.Biome.Chunks.TryGetValue(chunkType, out var chunk))
            throw new ArgumentException("Chunk does not exist in patch");

        if (species is not MicrobeSpecies microbeSpecies)
            return 0;

        var score = 1.0f;

        // Speed is not too important to chunk microbes
        // But all else being the same faster is better than slower
        score += MathF.Pow(cache.GetSpeedForSpecies(microbeSpecies), 0.4f);

        // Diminishing returns on storage
        score += (MathF.Pow(microbeSpecies.StorageCapacities.Nominal + 1, 0.8f) - 1) / 0.8f;

        // If the species can't engulf, then they are dependent on only eating the runoff compounds
        if (!microbeSpecies.CanEngulf ||
            cache.GetBaseHexSizeForSpecies(microbeSpecies) < chunk.Size * Constants.ENGULF_SIZE_RATIO_REQ)
        {
            score *= Constants.AUTO_EVO_CHUNK_LEAK_MULTIPLIER;
        }

        float compoundATP;
        if (compoundOut != atp)
        {
            var compoundOutGenerated =
                cache.GetCompoundGeneratedFrom(compound, compoundOut, microbeSpecies, patch.Biome);
            compoundATP = cache.GetCompoundConversionScoreForSpecies(compoundOut, atp, microbeSpecies, patch.Biome) *
                compoundOutGenerated;
        }
        else
        {
            compoundATP = cache.GetCompoundGeneratedFrom(compound, atp, microbeSpecies, patch.Biome);
        }

        var energyBalance = cache.GetEnergyBalanceForSpecies(microbeSpecies, patch.Biome);

        // Penalize species that don't actually need the compound generated by the chunk
        score *= MathF.Min(compoundATP / energyBalance.TotalConsumption, 1);

        return score;
    }

    public override LocalizedString GetDescription()
    {
        return new LocalizedString("CHUNK_FOOD_SOURCE", readableName);
    }

    public override float GetEnergy(Patch patch)
    {
        if (!patch.Biome.Chunks.TryGetValue(chunkType, out var chunk))
            throw new ArgumentException("Chunk does not exist in patch");

        if (chunk.Compounds?.TryGetValue(compound.ID, out var compoundAmount) != true)
            throw new ArgumentException("Chunk does not contain compound");

        // This computation nerfs big chunks with a large amount,
        // by adding an "accessibility" component to total energy.
        // Since most cells will rely on bigger chunks by exploiting the venting,
        // this technically makes it a less efficient food source than small chunks, despite a larger amount.
        // We thus account for venting also in the total energy from the source,
        // by adding a volume-to-surface radius exponent ratio (e.g. 2/3 for a sphere).
        // This logic doesn't match with the rest of auto-evo (which doesn't account for accessibility).
        // TODO: extend this approach or find another nerf.
        var ventedEnergy = MathF.Pow(compoundAmount.Amount, Constants.AUTO_EVO_CHUNK_AMOUNT_NERF);
        return ventedEnergy * chunk.Density * Constants.AUTO_EVO_CHUNK_ENERGY_AMOUNT;
    }

    public Compound GetUsedCompoundType()
    {
        return compound.ID;
    }

    public override string ToString()
    {
        var chunkName = Localization.Translate("CHUNK_FOOD_SOURCE").FormatSafe(readableName);

        return $"{Name} ({chunkName})";
    }
}
